package luceneqe.modelsIR;

import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;

import luceneqe.analysis.WordPreprocessor;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.TermQuery;


public final class BM25MultiTermScorer extends Scorer {

	private static Logger logger = Logger.getLogger(BM25MultiTermScorer.class
			.getCanonicalName());

	private static String FIELD = "text";

	private BM25TermScorer[] subScorer;
	private boolean[] state;
	private int currentPosition = Integer.MAX_VALUE;
	private boolean init = false;
	private float score = 0.0f;
	private Query query;
	private boolean isAnd;
	private Set<Term> terms = new HashSet<Term>();
	private IndexReader idx;
	private int numDocs;
	private String queryBoosted;
	private TreeMap<String,String> termsBoost;
	private String boostedQuery;
	
	protected BM25MultiTermScorer(Similarity similarity) {
		super(similarity);
	}

	public BM25MultiTermScorer(Similarity similarity, Query query,
			String[] fields, IndexReader idx,
			BM25MultiTermQuery.BooleanQuery booleanQuery, String boostedQuery) throws IOException {

		super(similarity);
		this.idx = idx;
		this.query = query;
		this.isAnd = (booleanQuery == BM25MultiTermQuery.BooleanQuery.AND);
		
		//Cargamos los boosts de la query
		this.boostedQuery = boostedQuery;
		this.boostMap();
		
		
		this.query.extractTerms(this.terms);
		Iterator<Term> iter = this.terms.iterator();
		int count = 0;
		this.subScorer = new BM25TermScorer[this.terms.size()];
		float boost = 1.0f;
		while (iter.hasNext()) {
			Term term = iter.next();
			if(this.termsBoost.keySet().contains(term.text())) {
				boost = Float.parseFloat(this.termsBoost.get(term.text()));
			}

			byte[] norms= idx.norms(term.field());
		
			this.subScorer[count++] = new BM25TermScorer(term, similarity,
					fields, idx, norms,
					similarity.idf(this.idx.docFreq(term), this.idx.numDocs()), boost);
					
			
		}
		this.state = new boolean[this.subScorer.length];
		this.numDocs = idx.numDocs();
	}

	public int doc() {
		return this.currentPosition;
	}

	private void initState() throws IOException {
		this.init = true;
		for (int i = 0; i < this.state.length; i++)
			this.state[i] = this.subScorer[i].next();
	}

	public Explanation explain(int doc) throws IOException {

		Explanation exp = new Explanation(this.score, "TOTAL");
		;
		if (this.skipTo(doc))
			exp = new Explanation(this.score, "Score for Document "
					+ this.doc());
		else
			exp = new Explanation(this.score, "NOT FOUND");

		for (int i = 0; i < this.state.length; i++) {
			exp.addDetail(this.subScorer[i].explain(doc));
		}

		return exp;
	}

	private boolean nextAnd() throws IOException {
		float sc = 0.0f;
		boolean blocked[] = new boolean[this.subScorer.length];
		for (int i = 0; i < blocked.length; i++)
			blocked[i] = false;
		// System.out.println("WORKING WITH AND");
		while (true) {
			sc = 0.0f;
			int position = Integer.MAX_VALUE;
			// LOOK FOR LOWER DOCUMENTS THAT MATCH TERM AND CALCULATE POSITION
			for (int i = 0; i < this.subScorer.length; i++) {
				if (this.state[i] && this.subScorer[i].doc() < position) {
					position = this.subScorer[i].doc();
					this.currentPosition = position;
				}
			}

			// CALCULATE SCORE AND ADVANCE TERM POSITIONS
			int count = 1;
			for (int i = 0; i < this.subScorer.length; i++) {
				if (!blocked[i] && this.state[i]
						&& (this.subScorer[i].doc() == this.currentPosition)) {
					blocked[i] = true;
					// sc += this.subScorer[i].score();
					// this.state[i] = this.subScorer[i].next();
					count++;
				}
			}

			if (count == this.subScorer.length + 1) {
				sc = 0.0f;
				for (int i = 0; i < blocked.length; i++) {
					sc += this.subScorer[i].score();
					this.state[i] = this.subScorer[i].next();
				}
				this.score = sc;
				return true;
			}

			for (int i = 0; i < blocked.length; i++) {
				if (blocked[i]) {
					this.state[i] = this.subScorer[i].next();
					blocked[i] = false;
				}
			}

			this.score = sc;
			if (count == 1)
				return false;
			if (count == this.subScorer.length + 1) {
				return true;
			}

		}

	}

	public boolean next() throws IOException {
		float sc = 0.0f;
		boolean result = false;
		// FILL STATE JUST FIRST TIME THAT IS INVOKED
		if (!this.init)
			this.initState();
		if (this.isAnd)
			return this.nextAnd();
		int position = Integer.MAX_VALUE;
		// LOOK FOR LOWER DOCUMENTS THAT MATCH TERM AND CALCULATE POSITION
		for (int i = 0; i < this.subScorer.length; i++) {
			if (this.state[i] && this.subScorer[i].doc() < position) {
				position = this.subScorer[i].doc();
				this.currentPosition = position;
			}
		}
		// CALCULATE SCORE AND ADVANCE TERM POSITIONS
		for (int i = 0; i < this.subScorer.length; i++) {
			if (this.state[i]
					&& (this.subScorer[i].doc() == this.currentPosition)) {
				
				//sc += ((BM25TermScorer) this.subScorer[i]).score(getDocumentLength(idx,this.currentPosition));
				sc +=  this.subScorer[i].score();
				this.state[i] = this.subScorer[i].next();
				result = true;

			}
		}
		this.score = sc;
		return result;
	}

	
	
	public float score() throws IOException {
		return this.score;
	}

	// TODO CHECK FOR HIGH TARGET
	public boolean skipTo(int target) throws IOException {

		boolean found = false;

		for (int i = 0; i < this.subScorer.length; i++)
			this.subScorer[i].reset();
		this.initState();
		while (!found && this.next()) {// && target >= this.doc())
			if (target == this.doc())
				found = true;
		}
		return found;

	}

	//REVISAR TERMINOS ORIGINALES DE LA QUERY Y REPESADO
	public void boostMap() {
		this.termsBoost = new TreeMap<String,String>();
		String[] words = this.boostedQuery.split(" ");
		for (int i = 0; i < words.length; i++) {
			if (words[i].trim().length() > 0) {
				if (words[i].contains("^")) {
					String[] par = words[i].split("\\^");
					String w = par[0];
					// w = WordPreprocessor.getSpanishStem(w);
					termsBoost.put(w, par[1]);
				} else {
					String w = words[i];
					// w = WordPreprocessor.getSpanishStem(w);
					termsBoost.put(words[i], "1.0f");
				}
			}
		}
	}
	
	public void setQueryBoosted(String query) {
		this.queryBoosted = query;
	}

}
